-module(kcp2).
-export([createKCP/1, send/1, recv/0, input/1, update/1]).
-include("kcppack.hrl").

% 在当前process里面创建一个ets表格
createKCP(Wsz) ->
	ets:new(kcpobj, [set, private, named_table]),
	ets:insert(kcpobj, {sendId, 0}),
	ets:insert(kcpobj, {winSz, Wsz}),
	ets:insert(kcpobj, {winStart, 0}),
	ets:insert(kcpobj, {winEnd, Wsz}),
	ets:insert(kcpobj, {ackWinStart, 0}),
	ets:insert(kcpobj, {ackWinEnd, Wsz*2}),
	ets:insert(kcpobj, {interval, 0.05}),
	ets:insert(kcpobj, {accTime, 0.0}),
	ets:insert(kcpobj, {maxRecvNum, 999}),


	ets:insert(kcpobj, {sendQueue, queue:new()}),
	ets:insert(kcpobj, {recvQueue, queue:new()}),
	ets:insert(kcpobj, {sendBuf, queue:new()}),
	ets:insert(kcpobj, {recvBuf, queue:new()}),
	ets:insert(kcpobj, {ackYet, sets:new()}),


	LoopListId = looplist:createLoopList(Wsz),
	ets:insert(kcpobj, {recvList, LoopListId}),

	ets:insert(kcpobj, {isClose, false}),
	ets:insert(kcpobj, {closeEventHandler, undefined}),
	ets:insert(kcpobj, {outputFunc, undefined}).

send(Data)->
	Pack = #kcppack{
		data = Data
	},
	SendQueue = ets:lookup(kcpobj, sendQueue),
	ets:insert(kcpobj, {sendQueue,  queue:in(Pack, SendQueue)}). 

recv() ->
	RecvBuf = ets:lookup(kcpobj, recvBuf),
	case queue:out(RecvBuf) of 
		{{value, Item}, Q} ->
			ets:insert(kcpobj, {recvBuf, Q}),
			Item;
		{empty, _Q} ->
			undefined
	end.


input(Data) ->
	Pack = #kcppack{},
	Pack1 = kcppacket:decodeData(Pack, Data),
	RecvQueue = ets:lookup(kcpobj, recvQueue),
	RecvQueue1 = queue:insert(RecvQueue, Pack1),
	ets:insert(kcpobj, {recvQueue, RecvQueue1}).
	

update(Delta) ->
	AccTime = ets:lookup(kcpobj, accTime) + Delta,
	ets:insert(kcpobj, {accTime, AccTime}),
	Interval = ets:lookup(kcpobj, interval),
	IsClose = ets:lookup(kcpobj, isClose),
	case (AccTime >= Interval) and  (not IsClose) of
		true ->
			ets:insert(kcpobj, {accTime, AccTime-Interval}),
			handleRecv(),
			handleAcked(),
			handleSend();
		false ->
			undefined
	end.

handleRecv() ->
	RecvQueue = ets:lookup(kcpobj, recvQueue),
	ets:insert(kcpobj, {recvQueue, queue:new()}),
	loopRecvQueue(RecvQueue).

loopRecvQueue(RecvQueue) ->
	loopRecvQueue(RecvQueue, queue:is_empty(RecvQueue)).

loopRecvQueue(RecvQueue, true) ->
	RecvQueue;

loopRecvQueue(RecvQueue, false) ->
	{{value, Item}, Q}= queue:out(RecvQueue),
	Seg = Item,
	checkSeg(Seg, Seg#kcppack.isAck),
	loopRecvQueue(Q).

checkSeg(Seg, 1) ->
	SendBuf = updateSendBuf(Seg, ets:lookup(kcpobj, sendBuf), []),
	ets:insert(kcpobj, {sendBuf, SendBuf});

checkSeg(Seg, 0) ->
	Result = checkInWin(Seg#kcppack.sn, ets:lookup(kcpobj, ackWinStart), ets:lookup(kcpobj, ackWinEnd)),
	checkSeg(Seg, Result);

checkSeg(Seg, true) ->
	AckWinStart = ets:lookup(kcpobj, ackWinStart),
	AckWinEnd = ets:lookup(kcpobj, ackWinEnd),
	WinSz = ets:lookup(kcpobj, winSz),
	SrvSn = Seg#kcppack.sn,
	sendAck(Seg),
	Result = checkInWin(SrvSn, AckWinStart+WinSz, AckWinEnd),
	moveAckWin(Result),
	addNewPack(SrvSn, Seg);

checkSeg(_Seg, false) ->
	undefined.

addNewPack(SrvSn, Seg) ->
	AckYet = ets:lookup(kcpobj, ackYet),
	Result = sets:is_element(SrvSn, AckYet),
	addNewPack(SrvSn, Seg, Result).

addNewPack(SrvSn, Seg, false) ->
	AckWinStart = ets:lookup(kcpobj, ackWinStart),
	AckYet = ets:lookup(kcpobj, ackYet),
	AckYet1 = sets:add_element(SrvSn, AckYet),
	ets:insert(kcpobj, AckYet1),
	RecvList = ets:lookup(kcpobj, recvList),
	RecvList1 = looplist:addPacket(RecvList, Seg, SrvSn-AckWinStart),
	ets:insert(kcpobj, {recvList, RecvList1}),
	checkLoopList();

addNewPack(_SrvSn, _Seg, true) ->
	undefined.

checkLoopList() ->
	RecvList = ets:lookup(kcpobj, recvList),
	{Item, R} = looplist:takePacket(RecvList),
	addRecvBuf(Item, R).

		
addRecvBuf(undefined, _R) ->
	undefined;

addRecvBuf(Item, R) ->	
	RecvBuf = ets:lookup(kcpobj, recvBuf),
	R1 = queue:in(Item, RecvBuf),
	ets:insert(kcpobj, {recvList, R}),
	ets:insert(kcpobj, {recvBuf, R1}).



moveAckWin(true) ->
	AckWinStart = ets:lookup(kcpobj, ackWinStart),
	AckWinEnd = ets:lookup(kcpobj, ackWinEnd),
	ets:insert(kcpobj, {ackWinStart, (AckWinStart+1) rem 4294967295}),
	ets:insert(kcpobj, {ackWinEnd, (AckWinEnd+1) rem 4294967295}),
	AckYet = ets:lookup(kcpobj, ackYet),
	AckYet1 = sets:del_element(AckWinStart, AckYet),
	ets:insert(kcpobj, AckYet1),
	RecvList = ets:lookup(kcpobj, recvList),
	RecvList1 = looplist:moveStart(RecvList),
	ets:insert(kcpobj, {recvList, RecvList1});

moveAckWin(false) ->
	undefined.


sendAck(Seg) ->
	Ack = #kcppack{isAck = 1, sn=Seg#kcppack.sn},
	Ack1 = kcppacket:encodeFull(Ack),
	Func = ets:lookup(kcpobj, outputFunc),
	Func(Ack1).



updateSendBuf(Seg, SendBuf, ResultQueue) ->
	updateSendBuf(Seg, SendBuf, queue:is_empty(SendBuf), ResultQueue).

updateSendBuf(Seg, SendBuf, false, ResultQueue) ->
	{{value, Item}, Q} = queue:out(SendBuf),
	IsSame = Item#kcppack.sn == Seg#kcppack.sn,
	Item1 = updateAcked(Item, IsSame),
	case IsSame of
		true ->
			queue:from_list(ResultQueue ++ [Item1] ++ queue:to_list(SendBuf));
		false ->
			updateSendBuf(Seg, Q, ResultQueue++[Item1])
	end;

updateSendBuf(_Seg, _SendBuf, true, ResultQueue) ->	
	queue:from_list(ResultQueue).


updateAcked(Item, true) ->
	Item#kcppack{cmd=cmd_acked};
updateAcked(Item, false) ->
	Item.






handleAcked() ->
	SendBuf = ets:lookup(kcpobj, sendBuf),
	ResultQueue = loopSendBuf(SendBuf, queue:new()),
	ets:insert(kcpobj, {sendBuf, ResultQueue}).

loopSendBuf(SendBuf, ResultQueue)->
	case queue:is_empty(SendBuf) of
		true ->
			ResultQueue;
		false ->
			Fir = queue:get(SendBuf),
			case Fir#kcppack.cmd == cmd_acked of
				true ->
					removeAcked(SendBuf, ResultQueue);
				false ->
					{{value, _Item}, Q} = queue:out(SendBuf),
					loopSendBuf(Q, queue:in(ResultQueue, Fir))
			end
	end.

removeAcked(SendBuf, ResultQueue) ->
	case queue:is_empty(ResultQueue) of
		true ->
			moveWind();
		false ->
			undefined
	end,
	{{value, _Item}, Q} = queue:out(SendBuf),	
	loopSendBuf(Q, ResultQueue).

	
	
% erlang need rem 
moveWind() ->
	WinStart = ets:lookup(kcpobj, winStart),
	WinEnd = ets:lookup(kcpobj, winEnd),
	ets:insert(kcpobj, {winStart, (WinStart+1) rem 4294967295}),
	ets:insert(kcpobj, {winEnd, (WinEnd+1) rem 4294967295}).


handleSend() ->
	putPackInWindow(),
	sendWin().


putPackInWindow() ->
	SendBuf = ets:lookup(kcpobj, sendBuf),
	case queue:len(SendBuf) < ets:lookup(kcpobj, winSz) of
		true ->
			prepareSend();
		false ->
			undefined
	end.

prepareSend() ->
	SendId = ets:lookup(kcpobj, sendId),
	case checkInWin(SendId,	ets:lookup(kcpobj, winStart), ets:lookup(kcpobj, winEnd)) of 
		true ->
			SendQueue = ets:lookup(kcpobj, sendQueue),
			case not queue:is_empty(SendQueue) of
				true ->
					moveQueueToBuf();
				false ->
					undefined
			end;
		false ->
			undefined
	end.


moveQueueToBuf() ->
	SendQueue = ets:lookup(kcpobj, sendQueue),
	{{value, Pack}, Q} = queue:out(SendQueue),
	ets:insert(kcpobj, {sendQueue, Q}),

	SendId = ets:lookup(kcpobj, sendId),
	ets:insert(kcpobj, SendId+1),

	SendBuf = ets:lookup(kcpobj, sendBuf),
	Pack1 = Pack#kcppack{cmd=cmd_push, ackTimeout=1, sn=SendId},
	Q1 = queue:in(Pack1, SendBuf),
	ets:insert(kcpobj, {sendBuf, Q1}).

	
checkInWin(Id, ST, End) ->
	case ST > End of
		true ->
			(Id >= ST) or (Id < End);
		false ->
			(Id >= ST) and (Id < End)
	end.

sendWin() ->
	SendBuf = ets:lookup(kcpobj, sendBuf),
	Rq = loopCheckSB(SendBuf, queue:new(), queue:is_empty(SendBuf)),
	ets:insert(kcpobj, Rq).


loopCheckSB(_SendBuf, ResultQueue, true) ->
	ResultQueue;


loopCheckSB(SendBuf, ResultQueue, false) ->
	{{value, Item}, Q} = queue:out(SendBuf),
	case Item#kcppack.cmd of
		cmd_push ->
			Pack1 = kcppacket:encodeFull(Item#kcppack{cmd=cmd_waitack, sendTime=getNow()}),
			Rq = queue:in(ResultQueue, Pack1),
			Func = ets:lookup(kcpobj, outputFunc),
			Func(Pack1),
			loopCheckSB(Q, Rq, queue:is_empty(Q));
		cmd_waitack ->
			Pack1 = resend(Item),
			Rq = queue:in(ResultQueue, Pack1),
			loopCheckSB(Q, Rq, queue:is_empty(Q))
	end.

resend(Item) ->
	AckTimeout = Item#kcppack.ackTimeout,
	case AckTimeout  =< 0 of
		true ->
			Pack1 = Item#kcppack{ackTimeout=1},
			Now = getNow(),
			case Now-Item#kcppack.sendTime > 1 of
				true ->
					close();
				false ->
					Func = ets:lookup(kcpobj, outputFunc),
					Func(Pack1)
			end,
			Pack1;
		false ->
			Item#kcppack{ackTimeout=AckTimeout-1}
	end.


close() ->
	ets:insert(kcpobj, {isClose, true}),
	CloseEvent = ets:lookup(kcpobj, closeEventHandler),
	CloseEvent().

getNow() ->
	erlang:system_time()/1000000000.











